﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved All Rights Reserved
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Dynamic;
using System.Text;
using System.Xml;
using System.Collections.Concurrent;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Windows.Threading;
using FinPlusUtility;
using u = FinPlusUtility.Utility;

namespace FinPlusCommon
{
    public enum FieldType { Id, Name, Value, Type, TimeStamp, Closed, User }

    public class Function : ObservableCollection<IFinPlusItem>, IFinPlusFunction 
    {
        //public
        public object this[string name, string user = null] { get { return Value(name); } set { Add(name, value, user == null ? Environment.UserName : user); } }
        public int Id { get { return _id; } set { _id = value; } }        
        public string Name { get{return _name; } set{ _name = value; } }
        public string Error { get { return _error; } set { _error = value; } }
        public bool IsClosed { get; set; }
        public bool IsDirty { get; set; }
        public IList<string> Parents { get { return _parents == null ? _parents = new List<string>() : _parents; } set { _parents = value; } }
        public IList<string> Children { get { return _children == null ? _children = new List<string>() : _children; ; } set { _children = value; } }
        public static IList<string> Protected { get { return _protected; } }
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        public string ServiceKey { get; set; }

        //set by function factory 
        public object Object { get; set; }
        public Type Type { get; set; }
        public EventHandler<ItemEventArgs> ItemUpdate { get; set; }
        public string Invoke { get; set; }

        //private
        private int _id = -1, _paramPos = 1;
        private IList<string> _parents, _children;
        private SortedDictionary<int, IFinPlusItem> _index = new SortedDictionary<int, IFinPlusItem>();
        private string _name = Params.Unbooked.ToString(), _error = Params.Unbooked.ToString(), _user;
        private enum Params { Id, FunctionName, Message, Function, TimeStamp, Tag, Child, Unbooked, Item, String, Unknown, DateTime }
        private enum DataType { String, Int32, DateTime, Int, Double, Function, Unknown }
        private static IList<string> _protected = new List<string>() { "TimeStamp", "Id", "FunctionName", "Parent" };

        //construct
        public Function()
        {
            _user = Environment.UserName;
        }

        public Function(string name, string id = null, string user = null, int tempId = -1, string tag = null)
        {
            _name = name;
            _user = user == null ? Environment.UserName : user;
            var dateTime = id == null ? DateTime.MinValue : DateTime.Now;
            _id = tempId;

            TryAdd(new Item(1, tempId, Params.Id.ToString(), id == null ? Params.Unbooked.ToString() : id, DataType.Int32.ToString(), dateTime, _user));
            TryAdd(new Item(2, tempId, Params.FunctionName.ToString(), name, DataType.String.ToString(), dateTime, _user));
            TryAdd(new Item(3, tempId, Params.TimeStamp.ToString(), Params.Unbooked.ToString(), DataType.DateTime.ToString(), dateTime, _user));
            if (tag != null) TryAdd(new Item(4, tempId, Params.Tag.ToString(), tag, DataType.String.ToString(), dateTime, _user));
        }

        public Function(string name, string message, IFinPlusFunction func, int tempId = -1)
        {
            _name = name;
            _user = Environment.UserName;
            var dateTime = DateTime.Now;
            _id = tempId;

            TryAdd(new Item(1, tempId, Params.Id.ToString(), tempId < 0 ? Params.Unbooked.ToString() : tempId.ToString(), DataType.Int32.ToString(), dateTime, _user));
            TryAdd(new Item(2, tempId, Params.FunctionName.ToString(), name, DataType.String.ToString(), dateTime, _user));
            TryAdd(new Item(3, tempId, Params.TimeStamp.ToString(), DateTime.Now.ToString(), DataType.DateTime.ToString(), dateTime, _user));
            Add(Params.Message.ToString(), message);
            Add(Params.Function.ToString(), func);
        }

        public Function(IEnumerable<IFinPlusItem> function)
        {
            foreach (var item in function.OrderBy(i => i.Index))
            {
                TryAdd(item);
                if (item.Name.Equals(Params.Id.ToString()))
                    int.TryParse(item.Value, out _id);
            }
        }

        public Function(object[,] array)
        {
            ParseItems(array);
        }

        public Function(object message)
        {
            var msg = (Messaging2.Message)message;
            IsClosed = msg.ActionType == Messaging2.ActType.Close ? true : false;
            ParseItems((object[,])msg.Value);
        }

        public Function(int id, XmlElement xmlFunc, IDictionary<string, string> replace = null)
        {
            Add(id, xmlFunc, replace);
        }

        //public
        public void ItemsUpdate(IFinPlusFunction func)
        {
            foreach (var item in func)
            {
                var i = Get(item.Name);
                if(item.TimeStamp> i.TimeStamp )
                    i.Set(item.Value);
            }
        }

        public void Refresh()
        {
            foreach (var item in this)
                item.Refresh();
        }

        public IFinPlusFunction Clone(int id)
        {
            var clone = new Function(this.Name, null, _user, id);
            clone.Id = id;

            foreach (var item in _index.Values)
                clone.TryAdd(item.Clone(id));

            clone.Get(Params.Id.ToString()).Set(id);
            clone.IsClosed = IsClosed;
            
            return clone;
        }

        public IFinPlusItem Add(string name, object value, string user = "", int id = -1)
        {
            return Add(name, NextKey(), id, value, user, DateTime.Now);
        }

        public IFinPlusItem Add(string name, int index, int id, object value, string user, DateTime timeStamp)
        {
            IFinPlusItem item;
 
            if (value.GetType().Name.Equals(DataType.DateTime.ToString()))
                item = new Item(index, id, name, ((DateTime)value).ToOADate().ToString(), value.GetType().Name, timeStamp, user);
            else
                item = new Item(index, id, name, value.ToString(), value.GetType().Name, timeStamp, user);

            this.TryAdd(item);
            return item;
        }

        public bool TryAdd(IFinPlusItem item, bool withHistory = false, bool newKey = false)
        {
            try
            {
                if (newKey || item.Index < 0) item.Index = NextKey();

                if (!History(item, withHistory)) return false;

                item.PropertyChanged += Item_Changed;
                
                base.Add(item);
                _index[item.Index] = item;

                if (item.Name.Equals(Params.FunctionName.ToString())) _name = item.Value;

                return true;
            }
            catch
            {
                return false;
            }
        }

        public void Add(IDictionary<string, string> items)
        {
            foreach (var kvp in items)
            {
                switch (u.EnumTryParse<DataType>(kvp.Value, DataType.Unknown))
                {
                    case DataType.String: this[kvp.Key] = DataType.Unknown.ToString(); break;
                    case DataType.DateTime: this[kvp.Key] = DateTime.MinValue; break;
                    case DataType.Double: this[kvp.Key] = 0.0; break;
                    case DataType.Int: this[kvp.Key] = 0; break;
                    case DataType.Function: this[kvp.Key] = new Function(); break;
                    case DataType.Unknown: this[kvp.Key] = DataType.Unknown.ToString(); break;
                }
            }
        }

        private void Add(int id, XmlElement xmlFunc, IDictionary<string, string> replace)
        {
            _id = id;

            if (xmlFunc.ChildNodes.Count > 0)
                Add(xmlFunc, replace);
            else
                Add(xmlFunc.Attributes, replace);
        }

        public void Add(XmlElement xmlItems, IDictionary<string, string> replace)
        {
            foreach (XmlElement xmlItem in xmlItems)
                if (xmlItem.Name.Equals(Params.Item.ToString()))
                    TryAdd(new Item(_paramPos++, _id, xmlItem, replace));
        }

        public void Add(XmlAttributeCollection xmlItems, IDictionary<string, string> replace)
        {
            foreach (XmlAttribute xmlItem in xmlItems)
                TryAdd(new Item(_paramPos++, _id, xmlItem.Name, xmlItem.Value, Params.String.ToString(), DateTime.MinValue, Params.Unknown.ToString(), replace));
        }

        public void Append(IFinPlusFunction func)
        {
            foreach (var item in func)
                if(item.Index > 3)
                    this.TryAdd(item, false, true);
        }

        public void Append(IFinPlusFunctions funcs)
        {
            foreach (var func in funcs.Values)
                Append(func);
        }

        public IFinPlusItem Get<T>(string name, T defaultValue, PropertyChangedEventHandler changeEventHander = null)
        {
            lock (this)
            {
                var item = Get(name, changeEventHander);
                
                if (item == null) item = Add(name, defaultValue, _user, Id);

                if (changeEventHander != null) item.PropertyChanged += changeEventHander;

                return item;
            }
        }

        public IFinPlusItem Get(string name, PropertyChangedEventHandler changeEventHander = null)
        {
            IFinPlusItem i = null;

            foreach (IFinPlusItem item in this)
            {
                if (item.Name != name) continue;

                i = item;
                break;
            }

            if (i != null && changeEventHander != null)  i.PropertyChanged += changeEventHander;

            return i;
        }

        public new void Remove(IFinPlusItem item)
        {
            base.Remove(item);
            if (_index.ContainsKey(item.Index))
                _index.Remove(item.Index);
        }

        public object Value(string name, object defaultValue = null)
        {
            foreach (IFinPlusItem i in this)
                if (i.Name == name) return i.Object;

            return defaultValue;
        }

        public string SortKey(string[] sortOrders)
        {
            return string.Join("|", (from sortOrder in sortOrders join items in this on sortOrder equals items.Name select items.Value).ToArray());
        }

        public bool IsEqual(IFinPlusFunction func)
        {
            var first =  string.Join(",", this.Select(f => f.Value).ToArray());
            var second = string.Join(",", func.Select(f => f.Value).ToArray());

            return first.Equals(second);
        }

        public bool IsMatch(string where)
        {
            if (string.IsNullOrEmpty(where)) return true;

            return this.AsQueryable().Where(where.Replace("'", "\"")).Any();
        }

        public override string ToString()
        {
            return u.StringFromArray2d(ToArray());
        }

        public string ToString(string[] DisplayFields)
        {
            return string.Join("$", (from field in DisplayFields join items in this on field equals items.Name select items.Display).ToArray());
        }

        public static IFinPlusFunction FromString(string function)
        {
            if (string.IsNullOrEmpty(function))
                return new Function();
            return new Function((object[,])u.StringToArray2d(function));
        }

        public object[,] ToArray()
        {
            var a = new object[this.Count(), 7];
            int i = 0;

            foreach (var item in _index.Values)
            {
                a[i, 0] = item.Id;
                a[i, 1] = item.Name;
                a[i, 2] = item.Value;
                a[i, 3] = item.Type;
                a[i, 4] = item.TimeStamp.ToOADate();
                a[i, 5] = item.Closed.ToOADate();
                a[i, 6] = item.User;
                i++;
            }

            return a;
        }

        public IList<IFinPlusItem> ToList(string match, bool isValMatch = true)
        {
            if(isValMatch)
                return this.Where(i => i.Value.Equals(match)).ToList();
            
            return this.Where(i => i.Name.Equals(match)).ToList();
        }

        public object[] ToParamArray(object child = null, IList<string> exclude = null, bool dateTimeToInt = false)
        {
           var objs = new List<object>();
           foreach (var item in _index.Values)
           {
               if ((exclude == null || !exclude.Contains(item.Name)) && !Protected.Contains(item.Name))
               {
                   if(item.Name.Equals(Params.Child.ToString()))
                        objs.Add(child);
                   else if (dateTimeToInt && item.Type.Equals(Params.DateTime.ToString()))
                        objs.Add((int)((DateTime)item.Object).ToOADate());
                   else
                       objs.Add(item.Object);
               }
           }

           return objs.ToArray();
        }

        public string ToParamString(string child = "")
        {
            var objs = new StringBuilder();

            foreach (var item in _index.Values)
            {
                if (!Protected.Contains(item.Name))
                {
                    if (item.Name.Equals(Params.Child.ToString()))
                        objs.Append(child);
                    else if (item.Type.Equals(DataType.String.ToString()))
                        objs.Append('"' + item.Value + '"');
                    else
                        objs.Append(item.Value);

                    objs.Append(",");
                }
            }

            return objs.ToString().TrimEnd(',');
        }

        public void ToXmlDoc(XmlDocument xmlDoc, string node)
        {
            var xmlNode = xmlDoc.SelectSingleNode(node, null);
            var xmlFuncNode = xmlDoc.CreateNode(XmlNodeType.Element, "_" + this.Value(Params.Id.ToString()), null);

            xmlNode.AppendChild(xmlFuncNode);
            foreach (var item in this)
                xmlFuncNode.AppendChild(item.ToXmlNode(xmlDoc));
        }

        public string Replace(string value)
        {


            var res = (string[][])u.ToJaggedArray(value, ';',',');

            foreach(var item in res)
            {
                if (item.Count() < 2) continue;

                if (!item[1].StartsWith("[") || !item[1].EndsWith("]")) 
                    continue;

                var i = Get(item[1].TrimStart('[').TrimEnd(']'));

                if (i != null)
                    value = value.Replace(item[1] , i.Value);
            }

            return value;
        }

        //private
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs a)
        {
            using (BlockReentrancy())
            {
                var collectionChanged = this.CollectionChanged;
                if (collectionChanged == null) return;
                foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
                {
                    var dispObj = (DispatcherObject)handler.Target;
                    if (dispObj != null)
                    {
                        var disp = dispObj.Dispatcher;
                        if (disp != null && !disp.CheckAccess())
                        {
                            disp.BeginInvoke((Action)(() => handler.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))), DispatcherPriority.DataBind);
                            continue;
                        }
                    }
                    handler.Invoke(this, a);
                }
            }
        }

        private string Value(string name)
        {
            foreach (IFinPlusItem i in this)
                if (i.Name == name) return i.Value;

            return string.Empty;
        }

        private void ParseItems(object[,] array)
        {
            var r = array.GetUpperBound(0);

            for (int i = 0; i <= r; i++)
            {
                int id = int.Parse(array[i, 0].ToString());
                string name = array[i, 1].ToString();
                string value = array[i, 2].ToString();
                string type = array[i, 3].ToString();

                DateTime timeStamp = DateTime.MinValue;
                if (array[i, 4].ToString() != "0.0") timeStamp = DateTime.FromOADate(double.Parse(array[i, 4].ToString()));

                string user = (array[i, 5] ?? "").ToString();

                var item = new Item(i, id, name, value, type, timeStamp, user);
                if (item.Name.Equals(Params.FunctionName.ToString()))
                {
                    _name = item.Value;
                    _id = item.Id;
                }

                this.TryAdd(item);
            }
        }

        private bool History(IFinPlusItem item, bool withHistory)
        {
            if (_index.ContainsKey(item.Index))
            {
                var ItemExisting = _index[item.Index];
                if (!withHistory && ItemExisting.TimeStamp < item.TimeStamp)
                    Remove(ItemExisting); 
                else
                    return false; 
            }

            return true;
        }

        private int NextKey()
        {
            return _index.Count() < 1 ? 0 : _index.Max(kvp => kvp.Key) + 1;
        }

        //events
        private void Item_Changed(object sender, PropertyChangedEventArgs a)
        {
           if(ItemUpdate != null)
               ItemUpdate.Invoke(this, new ItemEventArgs((IFinPlusItem)sender, ChangeType.Altered));
        }

        private void Item_Update(object sender, PropertyChangedEventArgs a)
        {
            var funcDynamic = (IFinPlusFuncDynamic)sender;
            var item = Get(a.PropertyName);
            if(funcDynamic.GetTimeStamp(a.PropertyName) > item.TimeStamp)
                item.Set(funcDynamic.GetValue(a.PropertyName), funcDynamic.GetTimeStamp(a.PropertyName), null, true);
        }
    }

    public class FunctionEventArgs : EventArgs
    {
        public IFinPlusFunction Function { get; private set; }
        public FunctionEventArgs(IFinPlusFunction function) { Function = function; }
    }
}























































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus